/*
 * Copyright 2010 Michael Nitsche
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.mnit.basis.daten.typ.zeit;

import java.util.Calendar;
import java.util.GregorianCalendar;

import de.mnit.basis.daten.typ.A_Datentyp;
import de.mnit.basis.daten.typ.zeit.format.FormatZeit;
import de.mnit.basis.fehler.Fehler;
import de.mnit.basis.fehler.system.F_Programm;

/**
 * @author Michael Nitsche
 *
 * TODO Testen!!!!!
 */
public class Datum extends A_Datentyp<S_Datum> implements S_Datum {

	private final int jahr;
	private final int monat;
	private final int tag;


	public Datum() {
		// Eigentlich unsinnig, aber sonst wird auf einem UTC-Rechner (Server) die Sommerzeit nicht erkannt.
//		gc.setTimeZone( TimeZone.getTimeZone("ECT") ); //TODO Prüfen!!!
		this(new GregorianCalendar());
	}

	public Datum(Calendar gc) {
		Fehler.objekt.wenn_Null(gc);
		jahr	=gc.get(Calendar.YEAR);
		monat	=gc.get(Calendar.MONTH)+1;
		tag		=gc.get(Calendar.DAY_OF_MONTH);
	}

	public Datum(long ms) {
		GregorianCalendar gc=new GregorianCalendar();
		gc.setTimeInMillis(ms);
		jahr	=gc.get(Calendar.YEAR);
		monat	=gc.get(Calendar.MONTH)+1;
		tag		=gc.get(Calendar.DAY_OF_MONTH);
	}

	public Datum(int jahr, int monat, int tag) {
		iPruefe(jahr, monat, tag);
		this.tag=tag;
		this.monat=monat;
		this.jahr=jahr;
	}

	public Datum(String datum) {
		Fehler.objekt.wenn_Null(datum);
		int laenge=datum.length();
		try {
			//2007-06-18
			if(datum.matches("[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]")) {
				this.jahr	=iStringZuZahl(4,datum.substring(1-1, 4));
				this.monat	=iStringZuZahl(2,datum.substring(6-1, 7));
				this.tag	=iStringZuZahl(2,datum.substring(9-1,10));
			}
			//18.06.2007
			else if(datum.matches("[0-3][0-9].[0-1][0-9].[1-2][0-9][0-9][0-9]")) {
				this.jahr	=iStringZuZahl(4,datum.substring(7-1,10));
				this.monat	=iStringZuZahl(2,datum.substring(4-1, 5));
				this.tag	=iStringZuZahl(2,datum.substring(1-1, 2));
			}
			//18.06.07
			else if(datum.matches("[0-3][0-9].[0-1][0-9].[0-9][0-9]")) {
				this.jahr	=Zeit.zuJahrLang(datum.substring(7-1,8));
				this.monat	=iStringZuZahl(2,datum.substring(4-1,5));
				this.tag	=iStringZuZahl(2,datum.substring(1-1,2));
			}
			//TODO prüfen! Theoretisch sollte dies mit Länge 5 auch schon passen! Danach Beispiel hier rein!
			else if(laenge>=6 && laenge<=10 && datum.matches("^[0-3]?[0-9]\\.[01]?[0-9]\\.[12]?[0-9]{1,3}$")) {
				String regex="^([0-9]+)\\.([0-9]+)\\.([0-9]+)$";
				String tjahr=datum.replaceFirst(regex, "$3");
				if(tjahr.length()==1 || tjahr.length()==3)
					throw Fehler.text.da_Ungueltig(datum);

				this.tag	=Integer.parseInt(datum.replaceFirst(regex, "$1"));
				this.monat	=Integer.parseInt(datum.replaceFirst(regex, "$2"));
				this.jahr	=tjahr.length()==2 ? Zeit.zuJahrLang(tjahr) : Integer.parseInt(tjahr);
			}
			//2010-5-1
			else if(laenge>=8 && laenge<=10 && datum.matches("^[12]?[0-9]{1,3}-[01]?[0-9]-[0-3]?[0-9]$")) {
				String regex="^([0-9]+)-([0-9]+)-([0-9]+)$";
				this.tag	=Integer.parseInt(datum.replaceFirst(regex, "$3"));
				this.monat	=Integer.parseInt(datum.replaceFirst(regex, "$2"));
				this.jahr	=Integer.parseInt(datum.replaceFirst(regex, "$1"));
			}
			else throw Fehler.text.da_Ungueltig(datum);
		}
		catch(F_Programm f) {
			throw Fehler.weitergeben(f, "Text ungültig!", datum);
		}
	}

	// NEU

	public static Datum neu()								{ return new Datum(); }
	public static Datum neu(Calendar gc)					{ return new Datum(gc); }
	public static Datum neu(long ms)						{ return new Datum(ms); }
	public static Datum neu(int jahr, int monat, int tag)	{ return new Datum(jahr,monat,tag); }
	public static Datum neu(String datum)					{ return new Datum(datum); }

	// GIB

	public int gJahr()	{ return this.jahr; }
	public int gMonat()	{ return this.monat; }
	public int gTag()	{ return this.tag; }

	public String toString()				{ return FormatZeit.zuText(this); }
	public String zuText(Object... format)	{ return FormatZeit.zuText(this, format); }
	public String zuTextEDV()				{ return FormatZeit.zuTextEDV(this); }

	public int diffTage(S_Datum d) {
		long aktuell=Zeit.zuMilliSek(this.jahr, this.monat, this.tag);
		long ziel   =Zeit.zuMilliSek(d.gJahr(), d.gMonat(), d.gTag());
		double diff_ms=ziel-aktuell;
		double dtage=diff_ms/1000/60/60/24;
		int tage= (int)(dtage);
		if(Math.abs((dtage-((int)dtage)))>0.9d) // Damit nicht auch andere Sachen gefiltert werden! Tests waren so alle ok!
			tage+= dtage>0 ? 1 : -1; //Manchmal geht eine Stunde ab, vmtl. Sommerzeit
		return tage;
	}

	public int diffMonate(S_Datum d) { //Ok, getestet!
		boolean dig=d.istGroesser(this);
		S_Datum klein=dig ? this : d;
		S_Datum gross=dig ? d : this;

		int erg=klein.diffJahre(gross)*12; //nur ganze Jahre
		int k1=gross.gMonat()-klein.gMonat();
		if(k1<0 || (k1==0 && gross.gTag()<klein.gTag()))
			k1+=12;
		erg+=k1;

		if(gross.gTag()<klein.gTag())
			erg--;
		if(!dig)
			erg=-erg;
		return erg;
	}

	public int diffJahre(S_Datum d) { //Ok, getestet!
		boolean dig=d.istGroesser(this);
		S_Datum klein=dig ? this : d;
		S_Datum gross=dig ? d : this;
		int erg=gross.gJahr()-klein.gJahr();
		if(gross.gMonat()<klein.gMonat())
			erg--;
		else if(gross.gMonat()==klein.gMonat() && gross.gTag()<klein.gTag())
			erg--;
		if(!dig)
			erg=-erg;
		return erg;
	}

	public S_Datum gPlusTage(int anzahl) { //Ok, getestet!
		long aktuell=Zeit.zuMilliSek(this.gJahr(), this.gMonat(), this.gTag());
		long neu=aktuell + ((long)anzahl)*24*60*60*1000;
		return Datum.neu(neu);
	}

	public S_Datum gPlusMonate(int anzahl) { //Ok, getestet!
		long aktuell=Zeit.zuMilliSek(this.gJahr(), this.gMonat(), this.gTag());
		GregorianCalendar gc=new GregorianCalendar();
		gc.setTimeInMillis(aktuell);
		gc.add(Calendar.MONTH, anzahl);
		return Datum.neu(gc);
	}

	public S_Datum gPlusJahre(int anzahl) { //Ok, getestet!
		long aktuell=Zeit.zuMilliSek(this.gJahr(), this.gMonat(), this.gTag());
		GregorianCalendar gc=new GregorianCalendar();
		gc.setTimeInMillis(aktuell);
		gc.add(Calendar.YEAR, anzahl);
		return Datum.neu(gc);
	}

	public boolean istGleich(S_Datum datum) {
		Fehler.objekt.wenn_Null(datum);
		return datum.gJahr()==gJahr() && datum.gMonat()==gMonat() && datum.gTag()==gTag();
	}

	public boolean istGroesser(S_Datum d) {
		long ziel=Zeit.zuMilliSek(d.gJahr(), d.gMonat(), d.gTag());
		long akt =Zeit.zuMilliSek(gJahr(), gMonat(), gTag());
		return akt>ziel;
	}

	// INTERN

	private void iPruefe(int jahr, int monat, int tag) {
		Fehler.zahl.wenn_Ausserhalb(1600, 2999, jahr);
		Fehler.zahl.wenn_Ausserhalb(1, 12, monat);
		Fehler.zahl.wenn_Ausserhalb(1, Zeit.gTageImMonat(jahr, monat), tag);
	}

	private int iStringZuZahl(int laenge, String t) {
		Fehler.text.wenn_Ausserhalb(laenge,laenge,t);
		String erg=t;//FILT.TEXT.trimmen(false,t);
		if(t.length()==0) Fehler.text.da_Ungueltig(t);
		if(!erg.equals(erg.trim())) Fehler.text.da_Ungueltig(t);
		try                  { return Integer.valueOf(erg).intValue(); }
		catch(Exception e)   { throw Fehler.text.da_Ungueltig(t); }
	}

}
